﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;
using System.IO;
using System.Xml;
using System.Windows.Forms;
using System.Xml.Serialization;
using ClassRoomServer.Commons;
using ClassRoomServer.Libs;
using ClassRoomServer.Packets;
using Newtonsoft.Json;
using DLULearningNetworkModel;

namespace ClassRoomServer
{
    // Packet's type
    public enum ObjectType
    {
        UserInfo,
        UserInRoom,
        DisconnectUser,
        ChatMessage,
        Webcam,
        Voice,
        BlackBoard,
        FullSlide,
        OneSlide,
        PPTSlidesInfo
    }

    public class MessengerServer
    {
        #region Fields

        private Socket listener;
        static private List<MessengerConnection> clients = new List<MessengerConnection>();
        static private List<UserInfo> _UserInfos = new List<UserInfo>();
        static private List<KeyValuePair<Guid, PPTSlidesInfo>> _listClassRoomIDvsPPTSlidesInfo = new List<KeyValuePair<Guid, PPTSlidesInfo>>();
        private bool isStopped;
        public delegate void dlgDeliverMessage(string message);
        public event dlgDeliverMessage OnDeliverMessage;
        private PowerpointConverter.ImageFormat _slideImageFormat = PowerpointConverter.ImageFormat.PNG;
        private int slideWidth = 570, slideHeight = 350;

        #endregion

        #region START / STOP / ONCONNECTION

        public void Start()
        {
            listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            listener.SetSocketOption(SocketOptionLevel.Tcp,
                (SocketOptionName)SocketOptionName.NoDelay, 0);

            // cac port duoc phep gan trong silverlight tu 4502 -> 4534
            listener.Bind(new IPEndPoint(IPAddress.Any, 4530));

            // thoi gian cho yeu cau ket noi
            listener.Listen(10);
            listener.BeginAccept(new AsyncCallback(OnConnection), null);
        }

        private void OnConnection(IAsyncResult ar)
        {
            if (isStopped) return;

            // Listen for the next client.
            listener.BeginAccept(new AsyncCallback(OnConnection), null);

            Socket client = listener.EndAccept(ar);

            // Create a new object to handle this connection.            
            MessengerConnection clientHandler = new MessengerConnection(client, this);
            clientHandler.Start();

            lock (clients)
            {
                clients.Add(clientHandler);
            }
        }

        public void Stop()
        {
            isStopped = true;
            if (listener != null)
            {
                try
                {
                    listener.Close();
                }
                catch (Exception err)
                {
                    Console.WriteLine(err.Message);
                }
            }

            foreach (MessengerConnection client in clients)
            {
                client.Close();
            }
        }

        #endregion

        public void DeliverMessage(byte[] message, int bytesRead, MessengerConnection mc)
        {
            try
            {
                byte[] dataType = new byte[4];
                Array.Copy(message, 0, dataType, 0, 4);
                ObjectType objType = (ObjectType)BitConverter.ToInt32(dataType, 0);
                string input = Encoding.ASCII.GetString(message, 4, bytesRead - 4);
                switch (objType)
                {
                    case ObjectType.UserInfo:
                        UserInfo userInfo = JsonConvert.DeserializeObject<UserInfo>(input);
                        if (userInfo.IsOwner)
                        {
                            Course entity = Course.GetCourseByID(userInfo.ClassRoomID);
                            if (entity != null)
                            {
                                entity.IsRunningOnline_b = true;
                                Course.Update(entity);
                            }
                        }

                        mc._UserInfo = userInfo;
                        SendUserInRoom(mc);

                        // Send PPT slides info & current ppt slide image (If have)
                        for (int i = 0; i < _listClassRoomIDvsPPTSlidesInfo.Count; i++)
                        {
                            if (_listClassRoomIDvsPPTSlidesInfo[i].Key == mc._UserInfo.ClassRoomID)
                            {
                                SendPPTSlidesInfoToClient(mc, _listClassRoomIDvsPPTSlidesInfo[i].Value);
                                SendOneSlideToClient(mc, _listClassRoomIDvsPPTSlidesInfo[i].Value.CurPPTSlide);
                                break;
                            }
                        }

                        if (!_UserInfos.Contains(mc._UserInfo))
                        {
                            _UserInfos.Add(userInfo);
                        }
                        else
                        {
                            return;
                        }

                        break;
                    case ObjectType.FullSlide:
                        // Test
                        FullSlide fullSlide = JsonConvert.DeserializeObject<FullSlide>(input);
                        int totalPPTSlides = SavePPTFile(fullSlide, mc._UserInfo.ClassRoomID);
                        PPTSlidesInfo pptSlidesInfo = new PPTSlidesInfo(1, totalPPTSlides);
                        _listClassRoomIDvsPPTSlidesInfo.Add(new KeyValuePair<Guid, PPTSlidesInfo>(mc._UserInfo.ClassRoomID, pptSlidesInfo));
                        if (totalPPTSlides > 0)
                        {
                            SendPPTSlidesInfoToClients(mc._UserInfo.ClassRoomID, pptSlidesInfo);
                            SendOneSlideToClients(mc._UserInfo.ClassRoomID, 1);
                        }

                        return;
                    case ObjectType.OneSlide:
                        int slide = JsonConvert.DeserializeObject<int>(input);
                        for (int i = 0; i < _listClassRoomIDvsPPTSlidesInfo.Count; i++)
                        {
                            if (_listClassRoomIDvsPPTSlidesInfo[i].Key == mc._UserInfo.ClassRoomID)
                            {
                                PPTSlidesInfo newPPTSlidesInfo = _listClassRoomIDvsPPTSlidesInfo[i].Value;
                                newPPTSlidesInfo.CurPPTSlide = slide;
                                _listClassRoomIDvsPPTSlidesInfo[i] = new KeyValuePair<Guid, PPTSlidesInfo>(
                                    _listClassRoomIDvsPPTSlidesInfo[i].Key, newPPTSlidesInfo);
                                break;
                            }
                        }

                        SendOneSlideToClients(mc._UserInfo.ClassRoomID, slide);
                        return;
                    case ObjectType.DisconnectUser:
                        DisconnectUser(mc._UserInfo);
                        return;
                }
            }
            catch { }

            // Duplicate the collection to prevent threading issues.
            MessengerConnection[] connectedClients;
            lock (clients)
            {
                connectedClients = clients.ToArray();
            }

            foreach (MessengerConnection client in connectedClients)
            {
                try
                {
                    if (client._UserInfo.ClassRoomID == mc._UserInfo.ClassRoomID && client._UserInfo.Username != mc._UserInfo.Username)
                    {
                        client.ReceiveMessage(message, bytesRead);
                    }
                }
                catch
                {
                    // Client is disconnected.
                    // Remove the client to avoid future attempts.
                    lock (clients)
                    {
                        clients.Remove(client);
                        DisconnectUser(client._UserInfo);
                    }

                    client.Close();
                }
            }
        }

        #region SEND USERS IN CLASSROOM

        /// <summary>
        /// Send all users info in specified classroom to this client
        /// </summary>
        /// <param name="mc"></param>
        private void SendUserInRoom(MessengerConnection mc)
        {
            try
            {
                mc.ReceiveMessage(_UserInfos.Where(ui => ui.ClassRoomID == mc._UserInfo.ClassRoomID
                    && ui.Username != mc._UserInfo.Username).ToList(), ObjectType.UserInRoom);
            }
            catch
            {
                // Client is disconnected.
                // Remove the client to avoid future attempts.
                lock (clients)
                {
                    clients.Remove(mc);
                    DisconnectUser(mc._UserInfo);
                }

                mc.Close();
            }
        }

        #endregion

        #region DISCONNECT USER

        static private void DisconnectUser(UserInfo uifo)
        {
            SendDisconnectedUserInfo(uifo);
            MessengerConnection[] connectedClients;
            lock (clients)
            {
                connectedClients = clients.ToArray();
            }

            foreach (MessengerConnection client in connectedClients)
            {
                if (client._UserInfo.ClassRoomID == uifo.ClassRoomID
                    && client._UserInfo.Username == uifo.Username)
                {
                    lock (clients)
                    {
                        clients.Remove(client);
                    }

                    client.Close();
                }
            }

            _UserInfos.Remove(uifo);

            // The classroom's owner is disconnected
            // Delete all files of this classroom in folder Temp
            // Disconnect all members in this classroom
            if (uifo.IsOwner)
            {
                // Change classroom status
                Course course = Course.GetCourseByID(uifo.ClassRoomID);
                if (course != null)
                {
                    course.IsRunningOnline_b = false;
                    Course.Update(course);
                }

                DirectoryInfo di = new DirectoryInfo(Application.StartupPath + StaticConfigs.SlidesLocation + @"\" +
                    uifo.ClassRoomID);
                if (di.Exists)
                {
                    di.Delete(true);
                }

                // Remove ppt slides info
                for (int i = 0; i < _listClassRoomIDvsPPTSlidesInfo.Count; i++)
                {
                    if (_listClassRoomIDvsPPTSlidesInfo[i].Key == uifo.ClassRoomID)
                    {
                        _listClassRoomIDvsPPTSlidesInfo.RemoveAt(i);
                        break;
                    }
                }

                // Disconnect all members in this classroom
                for (int i = 0; i < _UserInfos.Count; i++)
                {
                    if (_UserInfos[i].ClassRoomID == uifo.ClassRoomID)
                    {
                        DisconnectUser(_UserInfos[i]);
                    }
                }
            }
        }

        static private void SendDisconnectedUserInfo(UserInfo uifo)
        {
            MessengerConnection[] connectedClients;
            lock (clients)
            {
                connectedClients = clients.ToArray();
            }

            foreach (MessengerConnection client in connectedClients)
            {
                try
                {
                    if (client._UserInfo.ClassRoomID == uifo.ClassRoomID)
                    {
                        client.ReceiveMessage(uifo, ObjectType.DisconnectUser);
                    }
                }
                catch
                {
                    // Client is disconnected.
                    // Remove the client to avoid future attempts.
                    lock (clients)
                    {
                        clients.Remove(client);
                    }

                    client.Close();
                }
            }
        }

        #endregion

        #region POWERPOINT SLIDE

        /// <summary>
        /// Save file powerpoint into Temp folder. It returns total slides of this file ppt
        /// </summary>
        /// <param name="fullSlide"></param>
        /// <param name="classRoomID"></param>
        /// <returns>Total slides</returns>
        private int SavePPTFile(FullSlide fullSlide, Guid classRoomID)
        {
            string directoryLocation = Application.StartupPath + StaticConfigs.SlidesLocation + @"\" + classRoomID.ToString();
            DirectoryInfo di = new DirectoryInfo(directoryLocation);
            di.Create();
            string saveLocation = di.FullName + @"\Slide" + fullSlide.FileType;
            PowerpointConverter.ConvertToPPTFromBytesArray(fullSlide.Buffer, saveLocation);
            return PowerpointConverter.ConvertToImages(saveLocation, directoryLocation, _slideImageFormat, slideWidth, slideHeight);
        }

        private byte[] GetPPTSlide(Guid classRoomID, int slide)
        {
            string directoryLocation = Application.StartupPath + StaticConfigs.SlidesLocation + @"\" + classRoomID.ToString();
            DirectoryInfo di = new DirectoryInfo(directoryLocation);
            FileInfo fi = new FileInfo(directoryLocation + @"\slide" + slide.ToString() + "." + _slideImageFormat);
            if (fi.Exists)
            {
                byte[] buffer = new byte[fi.Length];
                using (FileStream fs = fi.OpenRead())
                {
                    fs.Read(buffer, 0, buffer.Length);
                }

                return buffer;
            }

            return null;
        }

        private void SendOneSlideToClient(MessengerConnection client, int slide)
        {
            byte[] buffer = GetPPTSlide(client._UserInfo.ClassRoomID, slide);

            lock (client)
            {
                try
                {
                    client.ReceiveMessage(new OneSlide(buffer, slide), ObjectType.OneSlide);
                }
                catch
                {
                    // Client is disconnected.
                    // Remove the client to avoid future attempts.
                    lock (clients)
                    {
                        clients.Remove(client);
                        DisconnectUser(client._UserInfo);
                    }

                    client.Close();
                }
            }
        }

        private void SendOneSlideToClients(Guid classRoomID, int slide)
        {
            byte[] buffer = GetPPTSlide(classRoomID, slide);

            MessengerConnection[] connectedClients;
            lock (clients)
            {
                connectedClients = clients.ToArray();
            }

            foreach (MessengerConnection client in connectedClients)
            {
                try
                {
                    if (client._UserInfo.ClassRoomID == classRoomID)
                    {
                        client.ReceiveMessage(new OneSlide(buffer, slide), ObjectType.OneSlide);
                    }
                }
                catch
                {
                    // Client is disconnected.
                    // Remove the client to avoid future attempts.
                    lock (clients)
                    {
                        clients.Remove(client);
                        DisconnectUser(client._UserInfo);
                    }

                    client.Close();
                }
            }
        }

        private void SendPPTSlidesInfoToClient(MessengerConnection client, PPTSlidesInfo pptSlidesInfo)
        {
            lock (client)
            {
                try
                {
                    client.ReceiveMessage(pptSlidesInfo, ObjectType.PPTSlidesInfo);
                }
                catch
                {
                    // Client is disconnected.
                    // Remove the client to avoid future attempts.
                    lock (clients)
                    {
                        clients.Remove(client);
                        DisconnectUser(client._UserInfo);
                    }

                    client.Close();
                }
            }
        }

        private void SendPPTSlidesInfoToClients(Guid classRoomID, PPTSlidesInfo pptSlidesInfo)
        {
            MessengerConnection[] connectedClients;
            lock (clients)
            {
                connectedClients = clients.ToArray();
            }

            foreach (MessengerConnection client in connectedClients)
            {
                if (client._UserInfo.ClassRoomID == classRoomID)
                {
                    SendPPTSlidesInfoToClient(client, pptSlidesInfo);
                }
            }
        }

        #endregion
    }
}
